package com.geekcoding.geekoj.service;

import cn.hutool.core.collection.CollectionUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.geekcoding.common.enums.State;
import com.geekcoding.common.exceptions.GeekRuntimeException;
import com.geekcoding.geekoj.dto.QuestionDTO;
import com.geekcoding.geekoj.dto.TestDetailDTO;
import com.geekcoding.geekoj.entity.*;
import com.geekcoding.geekoj.nums.CorrectResult;
import com.geekcoding.geekoj.nums.TestType;
import com.geekcoding.geekoj.repository.*;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;

@Service
@RequiredArgsConstructor
public class TestService {

    private final TestRepository testRepository;

    private final SubjectRepository subjectRepository;

    private final QuestionRepository questionRepository;

    private final UserQuestionFavorRepository userQuestionFavorRepository;

    private final TestAnswerRepository testAnswerRepository;

    /**
     * 生成本次用户试题
     *
     * @param type       答题类型
     * @param userEntity 当前用户
     * @return 试题
     */
    public Test create(TestType type, String subjectCode, UserEntity userEntity) {
        Subject subject = subjectRepository.queryByCode(subjectCode);
        return testRepository.nullOrCreate(type, subject, userEntity);
    }

    public QuestionDTO nextQuestion(String testId, UserEntity userEntity) {
        Test test = Optional.ofNullable(testRepository.queryById(testId)).orElseThrow(() -> new GeekRuntimeException("参数异常，练习不存在!"));
        Question question;
        if (TestType.SEQUENCE.equals(test.getType())) {
            question = sequenceNext(test, userEntity);
        } else if (TestType.RANDOM.equals(test.getType())) {
            question = randomNext(test, userEntity);
        } else if (TestType.SMART.equals(test.getType())) {
            question = smartNext(test, userEntity);
        } else {
            question = testNext(test, userEntity);
        }
        return QuestionDTO.convert(question, test);
    }

    private Question testNext(Test test, UserEntity userEntity) {
        LambdaQueryWrapper<TestAnswer> wrapper = Wrappers.lambdaQuery();
        wrapper.eq(TestAnswer::getTestId, test.getId())
                .eq(TestAnswer::getAnswerId, userEntity.getId());
        long count = testAnswerRepository.count(wrapper);
        if (count >= 50) {
//        if (count >= 3) {
            LambdaQueryWrapper<TestAnswer> successWrapper = Wrappers.lambdaQuery();
            successWrapper.eq(TestAnswer::getTestId, test.getId())
                    .eq(TestAnswer::getAnswerId, userEntity.getId())
                    .eq(TestAnswer::getCorrectResult, CorrectResult.YES);

            LambdaQueryWrapper<TestAnswer> errorWrapper = Wrappers.lambdaQuery();
            errorWrapper.eq(TestAnswer::getTestId, test.getId())
                    .eq(TestAnswer::getAnswerId, userEntity.getId())
                    .eq(TestAnswer::getCorrectResult, CorrectResult.NO);

            List<TestAnswer> successList = testAnswerRepository.list(successWrapper);
            List<TestAnswer> errorList = testAnswerRepository.list(errorWrapper);

            int score = CollectionUtil.isNotEmpty(successList) ? successList.size() * 2 : 0;

            test.setScore(score);
            test.setState(TestStatus.COMPLETED);
            test.setSuccessNum(CollectionUtil.isNotEmpty(successList) ? successList.size() : 0);
            test.setErrorNum(CollectionUtil.isNotEmpty(errorList) ? errorList.size() : 0);
            testRepository.saveOrUpdate(test);
            return null;
        }
        return questionRepository.nextRandom(test, userEntity);
    }

    private Question smartNext(Test test, UserEntity userEntity) {
        return questionRepository.nextRandom(test, userEntity);
    }

    private Question randomNext(Test test, UserEntity userEntity) {
        return questionRepository.nextRandom(test, userEntity);
    }


    private Question sequenceNext(Test test, UserEntity userEntity) {
        return questionRepository.nextSequence(test, userEntity);
    }

    /**
     * 题目收藏/取消收藏
     *
     * @param questionId 题目ID
     * @param userEntity 用户
     * @param state      收场状态
     * @return 用户题目收藏信息
     */
    @Transactional
    public UserQuestionFavor questionFavor(String questionId, UserEntity userEntity, State state) {
        Question question = Optional.ofNullable(questionRepository.queryById(questionId)).orElseThrow(() -> new RuntimeException("参数异常，题目不存在！"));
        UserQuestionFavor userQuestionFavor = userQuestionFavorRepository.nullOrCreate(question, userEntity);
        userQuestionFavor.setState(state);
        userQuestionFavorRepository.saveOrUpdate(userQuestionFavor);
        return userQuestionFavor;
    }

    public UserQuestionFavor userFavor(String questionId, UserEntity userEntity) {
        LambdaQueryWrapper<UserQuestionFavor> queryChainWrapper = Wrappers.<UserQuestionFavor>lambdaQuery().eq(UserQuestionFavor::getQuestionId, questionId).eq(UserQuestionFavor::getUserId, userEntity.getId());
        return userQuestionFavorRepository.getBaseMapper().selectOne(queryChainWrapper);
    }

    public QuestionDTO submit(String testId, String questionId, String answerValue, UserEntity userEntity) {
        Test test = Optional.ofNullable(testRepository.queryById(testId)).orElseThrow(() -> new GeekRuntimeException("参数异常，练习不存在!"));
        Question question = Optional.ofNullable(questionRepository.queryById(questionId)).orElseThrow(() -> new RuntimeException("参数异常，题目不存在！"));
        TestAnswer testAnswer = testAnswerRepository.nullOrCreate(test, question, userEntity);
        testAnswer.setAnswerValue(answerValue);
        testAnswer.judge(question.getAnswer());
        testAnswerRepository.saveOrUpdate(testAnswer);
        return QuestionDTO.convert(question, test);
    }

    public Question questionDetail(String questionId) {
        return questionRepository.queryById(questionId);
    }

    public Page<QuestionDTO> queryFavorByPage(Page<UserQuestionFavor> page, UserEntity userEntity) {
        QueryWrapper<UserQuestionFavor> wrapper = new QueryWrapper<>();
        wrapper.eq("user_id", userEntity.getId()).eq("state", State.NORMAL);
        Page<UserQuestionFavor> result = userQuestionFavorRepository.page(page, wrapper);
        Page<QuestionDTO> pageResult = Page.of(result.getCurrent(), result.getPages(), result.getTotal());
        pageResult.setRecords(result.getRecords().stream().map(q -> QuestionDTO.convert(
                questionRepository.queryById(q.getQuestionId()), null)).collect(Collectors.toList()));
        return pageResult;
    }

    public Page<QuestionDTO> queryErrorByPage(Page<TestAnswer> page, UserEntity userEntity) {
        QueryWrapper<TestAnswer> wrapper = new QueryWrapper<>();
        wrapper.eq("answer_id", userEntity.getId());
        wrapper.eq("correct_result", "NO");
        Page<TestAnswer> result = testAnswerRepository.page(page, wrapper);
        Page<QuestionDTO> pageResult = Page.of(result.getCurrent(), result.getPages(), result.getTotal());
        pageResult.setRecords(result.getRecords().stream().map(q -> QuestionDTO.convert(
                questionRepository.queryById(q.getQuestionId()),
                testRepository.queryById(q.getTestId()))).collect(Collectors.toList()));
        return pageResult;
    }

    public QuestionDTO answerDetail(String answerId) {
        TestAnswer testAnswer = testAnswerRepository.queryById(answerId);
        return QuestionDTO.convert(
                questionRepository.queryById(testAnswer.getQuestionId()),
                testRepository.queryById(testAnswer.getTestId()));
    }

    public List<Map<String, Object>> ranking() {
        return testAnswerRepository.rankingByCount();
    }

    public Page<QuestionDTO> historyPage(Page<TestAnswer> page, UserEntity userEntity) {
        QueryWrapper<TestAnswer> wrapper = new QueryWrapper<>();
        wrapper.eq("answer_id", userEntity.getId());
        wrapper.orderByDesc("create_time");
        Page<TestAnswer> result = testAnswerRepository.page(page, wrapper);
        Page<QuestionDTO> pageResult = Page.of(result.getCurrent(), result.getPages(), result.getTotal());
        pageResult.setRecords(result.getRecords().stream().map(q -> QuestionDTO.convert(
                questionRepository.queryById(q.getQuestionId()),
                testRepository.queryById(q.getTestId()))).collect(Collectors.toList()));
        return pageResult;
    }

    public TestDetailDTO testDetail(String testId, UserEntity userEntity) {
        Test test = Optional.ofNullable(testRepository.queryById(testId)).orElseThrow(() -> new GeekRuntimeException("参数异常，练习不存在!"));
        LambdaQueryWrapper<TestAnswer> successWrapper = Wrappers.lambdaQuery();
        successWrapper.eq(TestAnswer::getTestId, testId)
                .eq(TestAnswer::getAnswerId, userEntity.getId())
                .eq(TestAnswer::getCorrectResult, CorrectResult.YES);

        LambdaQueryWrapper<TestAnswer> errorWrapper = Wrappers.lambdaQuery();
        errorWrapper.eq(TestAnswer::getTestId, testId)
                .eq(TestAnswer::getAnswerId, userEntity.getId())
                .eq(TestAnswer::getCorrectResult, CorrectResult.NO);

        List<TestAnswer> successList = testAnswerRepository.list(successWrapper);
        List<TestAnswer> errorList = testAnswerRepository.list(errorWrapper);

        return TestDetailDTO.builder()
                .test(test)
                .successList(successList)
                .errorList(errorList)
                .score(CollectionUtil.isNotEmpty(successList) ? successList.size() * 2 : 0)
                .build();
    }

    public List<Test> personalTop5(UserEntity userEntity) {
        LambdaQueryWrapper<Test> lambdaQueryWrapper = Wrappers.lambdaQuery();
        lambdaQueryWrapper.eq(Test::getAnswerId, userEntity.getId())
                .eq(Test::getState, TestStatus.COMPLETED)
                .isNotNull(Test::getScore)
                .orderByDesc(Test::getCreateTime);
        return testRepository.list(lambdaQueryWrapper);
    }
}
